﻿using Fun.Extensions;
using Fun.FontDecode.OpenTypes;
using Fun.IO;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Fun.FontDecode.Viewer
{
    public partial class MainForm : Form
    {
        static IDictionary<string, string> FileExtensions = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            { ".ttf", "TrueType Fonts" },
            { ".eot", "Embedded OpenType Fonts" },
        };

        const int DefaultFontSize = 128;
        const string DefaultNameFormat = "&#x{0:X4};";

        public MainForm()
        {
            InitializeComponent();
        }

        Dictionary<string, string> FCharMap = new Dictionary<string, string>();
        OpenTypeParser FParser;
        
        private void FileBtn_Click(object sender, EventArgs e)
        {
            using (var dlg = new OpenFileDialog())
            {
                var selected = (FileSystemEntry)FileNameEdit.SelectedItem;
                dlg.InitialDirectory = selected == null
                    ? Application.StartupPath
                    : Path.GetDirectoryName(selected.FullName);
                dlg.FileName = selected?.FullName;
                dlg.Filter = FileExtensions.Aggregate("", (s, x) => s + $"|{x.Value} (*{x.Key})|*{x.Key}").Remove(0, 1);
                if (dlg.ShowDialog(this) == DialogResult.OK)
                {
                    var entry = new FileSystemEntry(dlg.FileName);
                    FileNameEdit.Items.Insert(0, entry);
                    FileNameEdit.SelectedIndex = 0;
                }
            }
        }

        FileSystemEntry[] EnumFileNames(string folder)
        {
            return Directory.EnumerateFiles(folder)
                .Where(x => FileExtensions.ContainsKey(Path.GetExtension(x)))
                .Select(x => new FileSystemEntry(x))
                .ToArray();
        }

        private void FolderBtn_Click(object sender, EventArgs e)
        {
            using (var dlg = new FolderBrowserDialog())
            {
                var selected = (FileSystemEntry)FileNameEdit.SelectedItem;
                dlg.SelectedPath = selected == null 
                    ? Application.StartupPath 
                    : Path.GetDirectoryName(selected.FullName);
                dlg.ShowNewFolderButton = false;
                if (dlg.ShowDialog(this) == DialogResult.OK)
                {
                    var files = EnumFileNames(dlg.SelectedPath);
                    if (files.Any())
                    {
                        FileNameEdit.Items.Clear();
                        FileNameEdit.Items.AddRange(files);
                        FileNameEdit.SelectedIndex = 0;
                    }
                    else
                        MessageBox.Show("No font files found.", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }
        }

        void ClearDisplay()
        {
            FParser = null;
            FCharMap.Clear();
            LMapEdit.Clear();
            RMapEdit.Clear();
        }

        private void ReloadBtn_Click(object sender, EventArgs e)
        {
            var entry = (FileSystemEntry)FileNameEdit.SelectedItem;
            if (entry == null)
                return;
            if (!File.Exists(entry.FullName))
                throw new FileNotFoundException("File does not exists.", entry.FullName);

            if (string.Equals(Path.GetExtension(entry.FullName), ".eot", StringComparison.OrdinalIgnoreCase))
            {
                var eotParser = new EmbeddedOpenTypeParser();
                eotParser.ParseFile(entry.FullName);
                FParser = new OpenTypeParser();
                FParser.Parse(eotParser.FontData);
            }
            else
            {
                FParser = new OpenTypeParser();
                FParser.ParseFile(entry.FullName);
            }

            FCharMap.Clear();
            var map = FParser.Get<OpenTypeParser.IndexMappingTable>("cmap");
            var gly = FParser.Get<OpenTypeParser.GlyphHeader[]>("glyf");
            foreach (var chm in map.CharMaps)
            {
                var g = gly[chm.StartGlyphID];
                for (var cc = chm.StartCharCode; cc <= chm.EndCharCode; cc++)
                {
                    var id = cc.ToString("X4");
                    FCharMap.Add(id, g.DataString);
                }
            }

            var sorted = FCharMap;//.OrderBy(x => x.Value);
            LMapEdit.Lines = sorted.Select(x => $"{x.Key}={x.Value}").ToArray();
            RMapEdit.Lines = sorted.Select(x => $"{x.Key}=").ToArray();

            CalcScroll();
            PaintPanel.Invalidate();
        }
        
        Size FDiaplaySize;
        void CalcScroll()
        {
            if (this.WindowState == FormWindowState.Minimized)
                return;

            var r = PaintPanel.ClientRectangle;
            r.Width -= GridScroll.Width;
            using (var g = Graphics.FromHwnd(PaintPanel.Handle))
            {
                FDiaplaySize = FParser.CalulateDisplaySize(g, r, DefaultFontSize, PaintPanel.Font);
                if (GridScroll.Visible = FDiaplaySize.Height > r.Height)
                {
                    GridScroll.Maximum = FDiaplaySize.Height - r.Height;
                    //vScrollBar1.LargeChange = Math.Min(vScrollBar1.Maximum, r.Height) / 2;
                    //vScrollBar1.SmallChange = 1;
                }
            }
        }

        private void GridScroll_Scroll(object sender, ScrollEventArgs e)
        {
            timer1.Enabled = false;
            timer1.Enabled = true;
        }
        
        private void PaintPanel_Resize(object sender, EventArgs e)
        {
            CalcScroll();
            timer1.Enabled = false;
            timer1.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Enabled = false;
            PaintPanel.Invalidate();
        }

        private void PaintPanel_Paint(object sender, PaintEventArgs e)
        {
            if (FParser == null)
                return;

            var r = e.ClipRectangle;
            r.Width -= GridScroll.Width;
            r.Offset((r.Width - FDiaplaySize.Width) / 2, -GridScroll.Value);
            //r.Offset(Math.Max(0, (r.Width - FDiaplaySize.Width) / 2), -GridScroll.Value);
            FParser.DrawGlyphs(e.Graphics, r, DefaultFontSize, DefaultNameFormat, 
                PaintPanel.Font, Brushes.Maroon, Brushes.Black);
        }

        private void CopySBtn_Click(object sender, EventArgs e)
        {
            if (RMapEdit.Lines.Length == 0)
                return;

            var values = RMapEdit.Lines
               .Select(x => x.Split('='))
               .Where(x => x.Length == 2 && !string.IsNullOrWhiteSpace(x[0].Trim()) && !string.IsNullOrWhiteSpace(x[1].Trim()))
               .ToDictionary(x => x[0].Trim(), x => x[1].Trim().ToUpper());
            if (!values.Any())
                throw new Exception("请在右侧输入对应值。");

            var text = values
                .OrderBy(x => x.Value)
                .Select(x => $"{{ \"{x.Key}\", \"{x.Value}\" }},")
                .Aggregate((a, b) => a + Environment.NewLine + b);
            Clipboard.SetText(text);
            MessageBox.Show(text);
        }

        private void CopyDBtn_Click(object sender, EventArgs e)
        {
            if (RMapEdit.Lines.Length == 0)
                return;

            var values = RMapEdit.Lines
               .Select(x => x.Split('='))
               .Where(x => x.Length == 2 && !string.IsNullOrWhiteSpace(x[0].Trim()) && !string.IsNullOrWhiteSpace(x[1].Trim()))
               .ToDictionary(x => x[0].Trim(), x => x[1].Trim().ToUpper());
            if (!values.Any())
                throw new Exception("请在右侧输入对应值。");

            var text = values
                .OrderBy(x => x.Value)
                .Select(x => $"{{ \"{FCharMap[x.Key]}\", \"{x.Value}\" }},")
                .Aggregate((a, b) => a + Environment.NewLine + b);
            Clipboard.SetText(text);
            MessageBox.Show(text);
        }
    }
}
